package cn.jjxx.modules.activiti.extend.graph;

import java.util.ArrayList;  
import java.util.Date;  
import java.util.HashMap;  
import java.util.List;  
import java.util.Map;  
  
import org.activiti.engine.ProcessEngine;  
import org.activiti.engine.history.HistoricActivityInstance;  
import org.activiti.engine.impl.HistoricActivityInstanceQueryImpl;  
import org.activiti.engine.impl.Page;  
import org.activiti.engine.impl.cmd.GetDeploymentProcessDefinitionCmd;  
import org.activiti.engine.impl.context.Context;  
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;  
import org.activiti.engine.impl.pvm.PvmActivity;  
import org.activiti.engine.impl.pvm.PvmTransition;  
import org.activiti.engine.impl.pvm.process.ActivityImpl;  
  
import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  
  
  
/** 
 * 根据历史，生成实时运行阶段的子图. 
 */  
public class ActivitiHistoryGraphBuilder {  
    /** 
     * logger. 
     */  
    private static Logger logger = LoggerFactory  
            .getLogger(ActivitiHistoryGraphBuilder.class);  
    private String processInstanceId;  
    private ProcessDefinitionEntity processDefinitionEntity;  
    private List<HistoricActivityInstance> historicActivityInstances;  
    private List<HistoricActivityInstance> visitedHistoricActivityInstances = new ArrayList<HistoricActivityInstance>();  
    private Map<String, Node> nodeMap = new HashMap<String, Node>();  
  
    public ActivitiHistoryGraphBuilder(String processInstanceId) {  
        this.processInstanceId = processInstanceId;  
    }  
  
    public Graph build() {  
        this.fetchProcessDefinitionEntity();  
        this.fetchHistoricActivityInstances();  
  
        Graph graph = new Graph();  
  
        for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {  
            Node currentNode = new Node();  
            currentNode.setId(historicActivityInstance.getId());  
            currentNode.setName(historicActivityInstance.getActivityId());  
            currentNode.setType(historicActivityInstance.getActivityType());  
            currentNode  
                    .setActive(historicActivityInstance.getEndTime() == null);  
            logger.debug("currentNode : {}:{}", currentNode.getName(),  
                    currentNode.getId());  
  
            Edge previousEdge = this.findPreviousEdge(currentNode,  
                    historicActivityInstance.getStartTime().getTime());  
  
            if (previousEdge == null) {  
                if (graph.getInitial() != null) {  
                   // throw new IllegalStateException("already set an initial.");  
                }else{
                	graph.setInitial(currentNode);
                }  
            } else {  
                logger.debug("previousEdge : {}", previousEdge.getName());  
            }  
  
            nodeMap.put(currentNode.getId(), currentNode);  
            visitedHistoricActivityInstances.add(historicActivityInstance);  
        }  
  
        if (graph.getInitial() == null) {  
            throw new IllegalStateException("cannot find initial.");  
        }  
  
        return graph;  
    }  
  
    /** 
     * 根据流程实例id获取对应的流程定义. 
     */  
    public void fetchProcessDefinitionEntity() {  
        String processDefinitionId = Context.getCommandContext()  
                .getHistoricProcessInstanceEntityManager()  
                .findHistoricProcessInstance(processInstanceId)  
                .getProcessDefinitionId();  
        GetDeploymentProcessDefinitionCmd cmd = new GetDeploymentProcessDefinitionCmd(  
                processDefinitionId);  
        processDefinitionEntity = cmd.execute(Context.getCommandContext());  
    }  
  
    public void fetchHistoricActivityInstances() {  
        HistoricActivityInstanceQueryImpl historicActivityInstanceQueryImpl = new HistoricActivityInstanceQueryImpl();  
        // historicActivityInstanceQueryImpl.processInstanceId(processInstanceId)  
        // .orderByHistoricActivityInstanceStartTime().asc();  
        // TODO: 如果用了uuid会造成这样排序出问题  
        // 但是如果用startTime，可能出现因为处理速度太快，时间一样，导致次序颠倒的问题  
        historicActivityInstanceQueryImpl.processInstanceId(processInstanceId)  
                .orderByHistoricActivityInstanceId().asc();  
  
        Page page = new Page(0, 100);  
        historicActivityInstances = Context  
                .getCommandContext()  
                .getHistoricActivityInstanceEntityManager()  
                .findHistoricActivityInstancesByQueryCriteria(  
                        historicActivityInstanceQueryImpl, page);  
    }  
  
    /** 
     * 找到这个节点前面的连线. 
     */  
    public Edge findPreviousEdge(Node currentNode, long currentStartTime) {  
        String activityId = currentNode.getName();  
        ActivityImpl activityImpl = processDefinitionEntity  
                .findActivity(activityId);  
        HistoricActivityInstance nestestHistoricActivityInstance = null;  
        String temporaryPvmTransitionId = null;  
  
        // 遍历进入当前节点的所有连线  
        for (PvmTransition pvmTransition : activityImpl  
                .getIncomingTransitions()) {  
            PvmActivity source = pvmTransition.getSource();  
  
            String previousActivityId = source.getId();  
  
            HistoricActivityInstance visitiedHistoryActivityInstance = this  
                    .findVisitedHistoricActivityInstance(previousActivityId);  
  
            if (visitiedHistoryActivityInstance == null) {  
                continue;  
            }  
  
            // 如果上一个节点还未完成，说明不可能是从这个节点过来的，跳过  
            if (visitiedHistoryActivityInstance.getEndTime() == null) {  
                continue;  
            }  
  
            logger.debug("current activity start time : {}", new Date(  
                    currentStartTime));  
            logger.debug("nestest activity end time : {}",  
                    visitiedHistoryActivityInstance.getEndTime());  
  
            // 如果当前节点的开始时间，比上一个节点的结束时间要早，跳过  
            if (currentStartTime < visitiedHistoryActivityInstance.getEndTime()  
                    .getTime()) {  
                continue;  
            }  
  
            if (nestestHistoricActivityInstance == null) {  
                nestestHistoricActivityInstance = visitiedHistoryActivityInstance;  
                temporaryPvmTransitionId = pvmTransition.getId();  
            } else if ((currentStartTime - nestestHistoricActivityInstance  
                    .getEndTime().getTime()) > (currentStartTime - visitiedHistoryActivityInstance  
                    .getEndTime().getTime())) {  
                // 寻找离当前节点最近的上一个节点  
                // 比较上一个节点的endTime与当前节点startTime的差  
                nestestHistoricActivityInstance = visitiedHistoryActivityInstance;  
                temporaryPvmTransitionId = pvmTransition.getId();  
            }  
        }  
  
        // 没找到上一个节点，就返回null  
        if (nestestHistoricActivityInstance == null) {  
            return null;  
        }  
  
        Node previousNode = nodeMap  
                .get(nestestHistoricActivityInstance.getId());  
  
        if (previousNode == null) {  
            return null;  
        }  
  
        logger.debug("previousNode : {}:{}", previousNode.getName(),  
                previousNode.getId());  
  
        Edge edge = new Edge();  
        edge.setName(temporaryPvmTransitionId);  
        previousNode.getOutgoingEdges().add(edge);  
        edge.setSrc(previousNode);  
        edge.setDest(currentNode);  
  
        return edge;  
    }  
  
    public HistoricActivityInstance findVisitedHistoricActivityInstance(  
            String activityId) {  
        for (int i = visitedHistoricActivityInstances.size() - 1; i >= 0; i--) {  
            HistoricActivityInstance historicActivityInstance = visitedHistoricActivityInstances  
                    .get(i);  
  
            if (activityId.equals(historicActivityInstance.getActivityId())) {  
                return historicActivityInstance;  
            }  
        }  
  
        return null;  
    }  
} 
